"""
This file contains all the classes used by has_access for error handling
"""


from django.utils.translation import gettext as _

from xmodule.course_metadata_utils import DEFAULT_START_DATE


class AccessResponse:
    """Class that represents a response from a has_access permission check."""
    def __init__(self, has_access, error_code=None, developer_message=None, user_message=None,
                 additional_context_user_message=None, user_fragment=None):
        """
        Creates an AccessResponse object.

        Arguments:
            has_access (bool): if the user is granted access or not
            error_code (String): optional - default is None. Unique identifier
                for the specific type of error
            developer_message (String): optional - default is None. Message
                to show the developer
            user_message (String): optional - default is None. Message to
                show the user
            additional_context_user_message (String): optional - default is None. Message to
                show the user when additional context like the course name is necessary
            user_fragment (:py:class:`~web_fragments.fragment.Fragment`): optional -
                An html fragment to display to the user if their access is denied
        """
        self.has_access = has_access
        self.error_code = error_code
        self.developer_message = developer_message
        self.user_message = user_message
        self.additional_context_user_message = additional_context_user_message
        self.user_fragment = user_fragment
        if has_access:
            assert error_code is None

    def __bool__(self):
        """
        Overrides bool().

        Allows for truth value testing of AccessResponse objects, so callers
        who do not need the specific error information can check if access
        is granted.

        Returns:
            bool: whether or not access is granted

        """
        return self.has_access

    __nonzero__ = __bool__

    def to_json(self):
        """
        Creates a serializable JSON representation of an AccessResponse object.

        Returns:
            dict: JSON representation
        """
        return {
            "has_access": self.has_access,
            "error_code": self.error_code,
            "developer_message": self.developer_message,
            "user_message": self.user_message,
            "additional_context_user_message": self.additional_context_user_message,
            "user_fragment": self.user_fragment,
        }

    def __repr__(self):
        return "AccessResponse({!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format(
            self.has_access,
            self.error_code,
            self.developer_message,
            self.user_message,
            self.additional_context_user_message,
            self.user_fragment,
        )

    def __eq__(self, other):
        if not isinstance(other, AccessResponse):
            return False

        return (
            self.has_access == other.has_access and
            self.error_code == other.error_code and
            self.developer_message == other.developer_message and
            self.user_message == other.user_message and
            self.additional_context_user_message == other.additional_context_user_message and
            self.user_fragment == other.user_fragment
        )


class AccessError(AccessResponse):
    """
    Class that holds information about the error in the case of an access
    denial in has_access. Contains the error code, user and developer
    messages. Subclasses represent specific errors.
    """
    def __init__(self, error_code, developer_message, user_message,
                 additional_context_user_message=None, user_fragment=None):
        """
        Creates an AccessError object.

        An AccessError object represents an AccessResponse where access is
        denied (has_access is False).

        Arguments:
            error_code (String): unique identifier for the specific type of
            error developer_message (String): message to show the developer
            user_message (String): message to show the user
            additional_context_user_message (String): message to show user with additional context like the course name
            user_fragment (:py:class:`~web_fragments.fragment.Fragment`): HTML to show the user

        """
        super().__init__(False, error_code, developer_message,
                         user_message, additional_context_user_message, user_fragment)


class StartDateError(AccessError):
    """
    Access denied because the course has not started yet and the user
    is not staff
    """
    def __init__(self, start_date, display_error_to_user=True):
        """
        Arguments:
            display_error_to_user: If True, display this error to users in the UI.
        """
        error_code = "course_not_started"
        if start_date == DEFAULT_START_DATE:
            developer_message = "Course has not started"
            user_message = _("Course has not started")
        else:
            developer_message = f"Course does not start until {start_date}"
            user_message = _("Course does not start until {}"  # lint-amnesty, pylint: disable=translation-of-non-string
                             .format(f"{start_date:%B %d, %Y}"))
        super().__init__(
            error_code,
            developer_message,
            user_message if display_error_to_user else None
        )


class StartDateEnterpriseLearnerError(AccessError):
    """
    Access denied because the course has not started yet and the user is not staff.  Use this error when this user is
    also an enterprise learner and enrolled in the requested course.
    """
    def __init__(self, start_date, display_error_to_user=True):
        """
        Arguments:
            display_error_to_user: If True, display this error to users in the UI.
        """
        error_code = "course_not_started_enterprise_learner"
        if start_date == DEFAULT_START_DATE:
            developer_message = "Course has not started, and the learner is enrolled via an enterprise subsidy."
            user_message = _("Course has not started")
        else:
            developer_message = (
                f"Course does not start until {start_date}, and the learner is enrolled via an enterprise subsidy."
            )
            user_message = _("Course does not start until {}"  # lint-amnesty, pylint: disable=translation-of-non-string
                             .format(f"{start_date:%B %d, %Y}"))
        super().__init__(
            error_code,
            developer_message,
            user_message if display_error_to_user else None
        )


class MilestoneAccessError(AccessError):
    """
    Access denied because the user has unfulfilled milestones
    """
    def __init__(self):
        error_code = "unfulfilled_milestones"
        developer_message = "User has unfulfilled milestones"
        user_message = _("You have unfulfilled milestones")
        super().__init__(error_code, developer_message, user_message)


class VisibilityError(AccessError):
    """
    Access denied because the user does have the correct role to view this
    course.
    """
    def __init__(self, display_error_to_user=True):
        """
        Arguments:
            display_error_to_user: Should a message showing that access was denied to this content
                be shown to the user?
        """
        error_code = "not_visible_to_user"
        developer_message = "Course is not visible to this user"
        user_message = _("You do not have access to this course")
        super().__init__(
            error_code,
            developer_message,
            user_message if display_error_to_user else None
        )


class MobileAvailabilityError(AccessError):
    """
    Access denied because the course is not available on mobile for the user
    """
    def __init__(self):
        error_code = "mobile_unavailable"
        developer_message = "Course is not available on mobile for this user"
        user_message = _("You do not have access to this course on a mobile device")
        super().__init__(error_code, developer_message, user_message)


class IncorrectPartitionGroupError(AccessError):
    """
    Access denied because the user is not in the correct user subset.
    """
    def __init__(self, partition, user_group, allowed_groups, user_message=None, user_fragment=None):
        error_code = "incorrect_user_group"
        developer_message = "In partition {}, user was in group {}, but only {} are allowed access".format(
            partition.name,
            user_group.name if user_group is not None else user_group,
            ", ".join(group.name for group in allowed_groups),
        )
        super().__init__(
            error_code=error_code,
            developer_message=developer_message,
            user_message=user_message,
            user_fragment=user_fragment
        )


class NoAllowedPartitionGroupsError(AccessError):
    """
    Access denied because the content is not allowed to any group in a partition.
    """
    def __init__(self, partition, user_message=None, user_fragment=None):
        error_code = "no_allowed_user_groups"
        developer_message = f"Group access for {partition.name} excludes all students"
        super().__init__(error_code, developer_message, user_message)


class EnrollmentRequiredAccessError(AccessError):
    """
    Access denied because the user must be enrolled in the course
    """
    def __init__(self):
        error_code = "enrollment_required"
        developer_message = "User must be enrolled in the course"
        user_message = _("You must be enrolled in the course")
        super().__init__(error_code, developer_message, user_message)


class IncorrectActiveEnterpriseAccessError(AccessError):
    """
    Access denied because the user must login with correct enterprise.
    """
    def __init__(self, enrollment_enterprise_name, active_enterprise_name):
        error_code = "incorrect_active_enterprise"
        developer_message = "User active enterprise should be same as EnterpriseCourseEnrollment enterprise."
        user_message = _("You are enrolled in this course with '{enrollment_enterprise_name}'. However, you are "
                         "currently logged in as a '{active_enterprise_name}' user. Please log in with "
                         "'{enrollment_enterprise_name}' to access this course.")
        user_message = user_message.format(
            enrollment_enterprise_name=enrollment_enterprise_name, active_enterprise_name=active_enterprise_name
        )
        super().__init__(error_code, developer_message, user_message)


class DataSharingConsentRequiredAccessError(AccessError):
    """
    Access denied because the user must give Data sharing consent before access it.
    """
    def __init__(self, consent_url):
        error_code = "data_sharing_access_required"
        developer_message = consent_url
        user_message = _("You must give Data Sharing Consent for the course")
        super().__init__(error_code, developer_message, user_message)


class AuthenticationRequiredAccessError(AccessError):
    """
    Access denied because the user must be authenticated to see it
    """
    def __init__(self):
        error_code = "authentication_required"
        developer_message = "User must be authenticated to view the course"
        user_message = _("You must be logged in to see this course")
        super().__init__(error_code, developer_message, user_message)


class OldMongoAccessError(AccessError):
    """
    Access denied because the course is in Old Mongo and we no longer support them. See DEPR-58.
    """
    def __init__(self, courselike):
        error_code = 'old_mongo'
        developer_message = 'Access to Old Mongo courses is unsupported'
        user_message = _('{course_name} is no longer available.').format(
            course_name=courselike.display_name_with_default,
        )
        super().__init__(error_code, developer_message, user_message)
